#!/usr/bin/env python3
"""Simple macro processor"""

#
# Copyright 2024  Odin Kroeger
#
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ALL WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#

#
# Modules
#

from contextlib import suppress
from filecmp import cmp
from getopt import getopt, GetoptError
from os import remove, rename, strerror
from os.path import dirname, basename, commonprefix, exists, join
from shutil import copy
from string import Template
# pylint: disable=redefined-builtin
from sys import argv, exit, stderr
from typing import Callable, NoReturn

import logging
import re

import sievemgr as mod


#
# Metadata
#

__author__ = 'Odin Kroeger'
__copyright__ = '2024 Odin Kroeger'
__version__ = '0.1'


#
# Functions
#

def error(*args, status: int = 1, **kwargs) -> NoReturn:
    """Log an err and :func:`exit <sys.exit>` with `status`.

    Arguments:
        args: Positional arguments for :func:`logging.error`.
        status: Exit status.
        kwargs: Keyword arguments for :func:`logging.error`.
    """
    logging.error(*args, **kwargs)
    exit(status)


def showhelp(func: Callable) -> NoReturn:
    """Print the docstring of `func` and :func:`exit <sys.exit>`."""
    assert func.__doc__
    lines = func.__doc__.splitlines()
    indented = re.compile(r'\s+').match
    prefix = commonprefix(list(filter(indented, lines)))
    for line in lines[:-1]:
        print(line.removeprefix(prefix))
    exit()


def showversion() -> NoReturn:
    """Print version to standard output and exit."""
    print(f"macrop {__version__}\nCopyright {__copyright__}")
    exit()


#
# Main
#

def main() -> NoReturn:
    """macrop - replace variables with dunder globals

    Usage: macrop template output

    Options:
        -V   Show version information.
        -h   Show this help screen.
    """
    progname = basename(argv[0])
    logging.basicConfig(format=f'{progname}: %(message)s')

    try:
        opts, args = getopt(argv[1:], 'hV', ['help', 'version'])
    except GetoptError as err:
        error(err, status=2)

    for opt, _ in opts:
        if opt in ('-h', '--help'):
            showhelp(main)
        if opt in ('-V', '--version'):
            showversion()

    try:
        source, target = args
    except ValueError:
        print(f'usage: {progname} [-h] source target', file=stderr)
        exit(2)

    swap = join(dirname(source), '.' + basename(source) + '.swp')
    macros = {k: v for k, v in mod.__dict__.items()
              if (re.fullmatch(r'__\w+__', k, re.A | re.I)
                  and isinstance(v, (int, str)))}
    try:
        with open(swap, 'w') as swapfile:
            with open(source) as sourcefile:
                for line in sourcefile:
                    sub = Template(line).safe_substitute(macros)
                    print(sub, file=swapfile, end='')
        if exists(target):
            if cmp(swap, target):
                exit(0)
            copy(target, target + '.bak')
        rename(swap, target)
    except FileNotFoundError as err:
        error(f'{err.filename}: {strerror(err.errno)}')
    except OSError as err:
        error(strerror(err.errno))
    finally:
        with suppress(FileNotFoundError):
            remove(swap)
    exit(0)


if __name__ == '__main__':
    main()
